package com.study.studyelasticsearch.client.transportClient;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.study.studyelasticsearch.client.EsAnalyzer;
import com.study.studyelasticsearch.client.EsDoc;
import com.study.studyelasticsearch.client.EsIndex;
import com.study.studyelasticsearch.util.es.curd.vo.AnalyzeVo;
import com.study.studyelasticsearch.util.es.curd.vo.DeleteDocVo;
import com.study.studyelasticsearch.config.EsConfig;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsResponse;
import org.elasticsearch.action.admin.indices.get.GetIndexResponse;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.transport.client.PreBuiltTransportClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.net.InetSocketAddress;
import java.util.*;
import java.util.stream.Collectors;

/**
 * ES客户端官方文档：https://www.elastic.co/guide/en/elasticsearch/client/java-api/index.html
 * 参考ESTcpQuery
 */
@Component
public class TransportEsClient implements EsIndex, EsDoc, EsAnalyzer {

    private static final Logger logger = LoggerFactory.getLogger(TransportEsClient.class);

    private static TransportClient client = null;

    private static TransportClient client() {
        if (null != client) {
            return client;
        }
        Settings settings = Settings.builder()
                //.put("client.transport.sniff", EsConfig.getSniff())
                //.put("cluster.name", EsConfig.getClusterName())
                .build();
        client = new PreBuiltTransportClient(settings)
                .addTransportAddress(new TransportAddress(new InetSocketAddress(EsConfig.getIp(), EsConfig.getTcpPort())));
        return client;
    }

    public TransportClient getClient() {
        return client();
    }

    /**
     * @param indexName 索引名称
     * @return 判断indexName是否存在
     */
    @Override
    public boolean indexExist(String indexName) {
        return indexExist(indexName, false);
    }

    @Override
    public boolean indexExist(String indexName, boolean isExact) {
        if(!isExact){
            indexName = "*" + EsIndex.handleIndexName(indexName) + "*";
        }
        IndicesExistsResponse response = client().admin().indices().prepareExists(indexName).get();
        return response.isExists();
    }

    @Override
    public <T> void saveOrUpdateDoc(String indexName, T doc, String docId) {
        indexName = EsIndex.handleIndexName(indexName);
        String id = StrUtil.blankToDefault(docId, UUID.fastUUID().toString(true));
        Map<String, Object> docMap = BeanUtil.beanToMap(doc);
        logger.info("\nPUT /{}/_doc/{} \n{}", indexName, id, JSON.toJSONString(docMap));

        BulkRequest request = new BulkRequest().add(
                new UpdateRequest().index(indexName)
                        .id(id)
                        .doc(docMap)
                        // 缺省时是否新增，默认为false。为false时如果id对应的文档不存在则会抛出异常；为true时id对应的文档不存在则新增，但index的默认分词器为standard
                        .docAsUpsert(true)
        );
        BulkResponse response = client().bulk(request).actionGet();
    }

    @Override
    public List<DeleteDocVo> deleteDoc(String indexName, String... ids) {
        String _indexName = EsIndex.handleIndexName(indexName);
        return Arrays.stream(ids).map(id -> {
            logger.info("\nDELETE /{}/_doc/{}", _indexName, id);

            DeleteResponse response = client().delete(new DeleteRequest(_indexName, id)).actionGet();
            return new DeleteDocVo(_indexName, id, response.getResult().getLowercase());
        }).collect(Collectors.toList());
    }

    @Override
    public boolean deleteAllDoc(String indexName) {
        // TODO 这个暂时不知道用java怎么实现
//        POST /user/_delete_by_query
//        {
//            "query": {
//            "match_all": {}
//        }
//        }
        return false;
    }

    @Override
    public void updateByQuery() {

    }

    @Override
    public String[] listIndexs() {
        logger.info("\n GET /_cat/indices");
        GetIndexResponse getIndexResponse = client().admin().indices().prepareGetIndex().get();
        return getIndexResponse.getIndices();
    }

    @Override
    public String[] listIndexs(String indexName, boolean isExact) {
        if(!isExact) {
            indexName = "*" + EsIndex.handleIndexName(indexName) + "*";
        }
        GetIndexResponse getIndexResponse = client().admin().indices().prepareGetIndex().addIndices(indexName).get();
        return getIndexResponse.getIndices();
    }

    @Override
    public String[] listIndexs(String indexName) {
        return this.listIndexs(indexName, true);
    }

    @Override
    public boolean createIndex(String indexName) {
        return this.createIndex(indexName, Analyzer.ik_max_word);
    }

    @Override
    public boolean createIndex(String indexName, Analyzer analyzer) {
        indexName = EsIndex.handleIndexName(indexName);

        logger.info("\n" +
                "PUT /{}\n" +
                "{\n" +
                "    \"settings\" : {\n" +
                "        \"index\" : {\n" +
                "            \"analysis.analyzer.default.type\": \"{}\"\n" +
                "        }\n" +
                "    }\n" +
                "}", indexName, analyzer.name());
        // 如何创建一个有mapping的表？
        // TransportClient好像都有2套写法，一套是prepare，一套不是。prepare好像是全链式写法
        CreateIndexResponse response = client().admin().indices().prepareCreate(indexName)
                .setSettings(Settings.builder().put(EsIndex.Config.ANALYZER_TYPE, analyzer.name()))
                .get();
        return response.isAcknowledged();
    }

    @Override
    public boolean deleteIndexIfExist(String... indexNames) {
        Set<String> indexNameSet = Arrays.stream(indexNames).map(EsIndex::handleIndexName).filter(this::indexExist).collect(Collectors.toSet());
        return this.deleteIndex(indexNameSet);
    }

    @Override
    public boolean deleteIndex(String... indexNames) {
        return this.deleteIndex(Arrays.asList(indexNames));
    }

    @Override
    public boolean deleteIndex(Collection<String> indexNames) {
        // DELETE /user1,user2
        // 如果user1不存在，user2存在，则会报错并且user2不会被删除
        // 如果user1存在，user2不存在，则会报错但user1会删除
        // 这个相当于循环执行delete index，并且抛异常会中断后面的执行
        Set<String> indexNameSet = indexNames.stream().map(EsIndex::handleIndexName).collect(Collectors.toSet());
        String indexNameStr = CollUtil.join(indexNameSet, ",");
        logger.info("\n DELETE /{}", indexNameStr);

        AcknowledgedResponse response = client().admin().indices().delete(
                new DeleteIndexRequest(indexNameSet.toArray(new String[0]))
        ).actionGet();
        return response.isAcknowledged();
    }


    @Override
    public List<AnalyzeVo> analyze(Analyzer analyzer, String text) {
        // TODO，暂时不知道怎么实现
//        GET _analyze
//        {
//            "analyzer": "ik_max_word",
//                "text": ["我是中文"]
//        }
        return null;
    }
}
